package org.judo.components;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.judo.admin.identity.AppUser;
import org.judo.application.Application;
import org.judo.components.state.GroupScope;
import org.judo.components.state.ComponentPersistence;
import org.judo.components.state.SessionScope;
import org.judo.database.Database;
import org.judo.database.JudoDBConnection;
import org.judo.database.JudoDatabaseManager;
import org.judo.database.exceptions.DatabaseConnectionException;
import org.judo.propertyobject.PropertyNotFoundException;
import org.judo.propertyobject.PropertyObject;
import org.judo.service.IncorectServiceTypeException;
import org.judo.service.JudoService;
import org.judo.service.ServiceManager;
import org.judo.service.ServiceNotFoundException;
import org.judo.util.Util;

public class ComponentManager
{
	private static HttpSession serverSession;
	private static Application application;
	
	
	/* 
	 * -- Any databases it uses are populated
	 * -- Any properties defined for it are injected
	 * -- injection from spring??
	 *  
	 *  Page Controllers can have the following scopes:  
	 *  	Application  
	 *  	Session 
	 *  	Component Group 
	 *  	Request 
	 *  Later:
	 *    	Business Process
	 *    	Persistent 
	 */
	public static JudoComponent loadComponent(HttpServletRequest req,Class type,HashMap connections)
		throws ComponentCreationException
	{
		JudoComponent component = null;
		
		try
		{	
			ComponentPersistence cp = 
				ComponentPersistence.getInstance(serverSession);
			
			GroupScope groupAnnotation = (GroupScope)
				type.getAnnotation(GroupScope.class);
			
			if (groupAnnotation != null)
			{
				/*
				 * It has group scope
				 */
				component = cp.getGroupComponent(
						type,groupAnnotation.value(),connections);
			}
			else
			{
				SessionScope statefulAnnotation = (SessionScope)
					type.getAnnotation(SessionScope.class);
				
				/*
				 * See if it has Session scope.  If
				 * not just create a new one
				 */
				if (statefulAnnotation != null)
					component = cp.getStatefulComponent(type,connections);
				else
				{
					/*
					 * It is a request scoped Component
					 */
					component = (JudoComponent)req.getAttribute(type.getCanonicalName());
					
					if (component == null)
					{
						component = createNewComponent(type,connections);
						req.setAttribute(type.getCanonicalName(),component);
					}
				}
			}
			
			/*
			 * This is the injection of request specific
			 * data
			 */
			setDBsAndConnections(component,connections);
			
			/*
			 * TODO: Is this being called on other
			 * scope creations?
			 */
			if (!component.inited())
			{
				component.componentCreated();
				component.inited(true);
			}
		}
		catch(MustBeLoggedInException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new ComponentCreationException(
					"Could not create component '"+type.getCanonicalName()+
						"' because "+e);
		}
		
		return component;
	}
	
	public static void setDBsAndConnections(JudoComponent component,HashMap connections) 
		throws InjectionException
	{
		List<String> databaseNames = component.databaseNames();
		
		for (int i=0;i<databaseNames.size();i++)
		{
			String name = databaseNames.get(i);
			Class type = component.sessionType(name);
			Database db = getDatabaseService(component,name,type,connections);
			component.populateDatabase(name,db);
		}
	}
	
	public static JudoComponent loadComponent(HttpServletRequest req,String type,HashMap connection)
		throws ComponentCreationException, ClassNotFoundException
	{
		try
		{
			return loadComponent(req,Class.forName(type),connection);
		}
		catch(ClassNotFoundException e)
		{
			throw e;
		}
		catch(ComponentCreationException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new ComponentCreationException(
					"Could not create component '"+type+
						"' because "+e);
		}
	}
	
	//
	//TODO: Add optional Spring instantiation of the class
	//
	public static JudoComponent createNewComponent(Class type,HashMap connections)
		throws Exception
	{ 
		Object obj = type.newInstance();
		
		if (!(JudoComponent.class.isAssignableFrom(type)))
			throw new ComponentCreationException("Error attempting to load '"+type.getCanonicalName()+"'.  Components must extends 'JudoComponent'."); 
			
		JudoComponent component = (JudoComponent)obj;
		
		component.creationTime(new Date().getTime());
		component.populateApplication(application);
		injectServiceProperties(component,connections);
		manageSessions(component);
		manageUserInjection(component);
		
		return component;
	}
	
	private static void manageUserInjection(JudoComponent component)
		throws MustBeLoggedInException
	{
		AppUser user = (AppUser)serverSession.getAttribute("__USER");
		
		if (component.declaresUser())
		{
			if (user == null)
			{
				throw new MustBeLoggedInException();
			}
			
			component.populateUser(user);
		}
	}

	public static void removeFromSession(String name)
	{
		serverSession.removeAttribute(name);
	}
	
	private static void manageSessions(JudoComponent component)
		throws InjectionException
	{
		List<String> sessionNames = component.sessionNames();
		
		for (int i=0;i<sessionNames.size();i++)
		{
			String name = sessionNames.get(i);
			Class type = component.sessionType(name);
			
			String sessionId = type.getCanonicalName();
			
			Session sessionObj = (Session)
				serverSession.getAttribute(sessionId);
			
			if (sessionObj == null)
			{
				try
				{
					sessionObj = (Session)type.newInstance();
					serverSession.setAttribute(sessionId,sessionObj);
				} 
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			
			component.populateSession(name,sessionObj);
		}
	}
	
	private static void injectServiceProperties(
			JudoComponent component, HashMap connections)
				throws InjectionException
	{
		List<String> serviceNames = component.serviceNames();
		
		for (int i=0;i<serviceNames.size();i++)
		{
			try
			{
				String name = serviceNames.get(i);
				Class type = component.serviceType(name);
				JudoService service = ServiceManager.locateService(name,type);
				component.populateService(name,service);
			} 
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	private static Database getDatabaseService(
			JudoComponent component,String propName,Class type,HashMap connections)
				throws InjectionException
	{
		Database db  = JudoDatabaseManager.getDatabase(propName);
		
		if (connections == null)
		{
			JudoDBConnection con;
			
			try
			{
				con = JudoDatabaseManager.getConnection(propName);
			} 
			catch (DatabaseConnectionException e)
			{
				e.printStackTrace();
				throw new InjectionException("Error getting database '"+propName+"' in component '"+component.getClass().getCanonicalName()+"' because: "+e); 
			}
			
			component.populateConnection(propName,con);
			db.populateTransactionalConnection(con);
		}
		else
		{
			JudoDBConnection con = 
				(JudoDBConnection)connections.get(propName);
			
			if (con == null)
				throw new InjectionException("Internal Judo Error: The component of type: '"+component.getClass().getCanonicalName()
						+"' was expected to have a database connection for a database named '"+propName+"' but didn't.  Please report as a bug.");
			
			db.populateTransactionalConnection(con);
		}
		
		return db;
	}

	public static Application getApplication()
	{
		return application;
	}

	public static void setApplication(Application application)
	{
		ComponentManager.application = application;
	}

	public static void populateSession(HttpSession session)
	{
		serverSession = session;
	}
}
